//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"

namespace Noise
{

	//
	// Math Function Source with 3 arguments
	//

	template <typename SrcFunc, ESource SourceType>
	class CMathSource3Args : public CSource
	{
	// types
	protected:
		typedef CMathSource3Args< SrcFunc, SourceType >	Self;


	// variables
	protected:
		CSourcePtr	_src0,
					_src1,
					_src2;
		
		static const
			ESource	_sourceType = SourceType;

	// methods
	public:
		CMathSource3Args ()
		{}

		~CMathSource3Args ()
		{}

		ESourceState GetState () const
		{
			return (_src0.IsNotNull() and _src1.IsNotNull() and _src2.IsNotNull()) ?
					SOURCE_COMPLETE : UNCOMPLETE_ATTACHMENT_SOURCE;
		}
		
		virtual void Delete ()
		{
			_src0 = null;
			_src1 = null;
			_src2 = null;
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT :
				{
					int	count0 = 0,
						count1 = 0,
						count2 = 0;

					_src0->GetInteger( SOURCES_COUNT, &count0, 1 );
					_src1->GetInteger( SOURCES_COUNT, &count1, 1 );
					_src2->GetInteger( SOURCES_COUNT, &count2, 1 );

					v[0] = count0 + count1 + count2 + 1;
					return OK;
				}

				case INPUT_SOURCES :
					v[0] = 3;
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, CSourcePtr src)
		{
			switch ( type )
			{
				case SOURCE_0 :	_src0 = src;	return OK;
				case SOURCE_1 :	_src1 = src;	return OK;
				case SOURCE_2 :	_src2 = src;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, CSourcePtr &src) const
		{
			switch ( type )
			{
				case SOURCE_0 :	src = _src0;	return OK;
				case SOURCE_1 :	src = _src1;	return OK;
				case SOURCE_2 :	src = _src2;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual CSourcePtr Clone () const override
		{
			return new Self();
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
			data.Push( _src0.ptr() );
			data.Push( _src1.ptr() );
			data.Push( _src2.ptr() );
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			CSource::BuildFunctionHeader( data.source, _id );
			data.source << " ( ";
			SrcFunc::Build( data, _src0->GetSourceId(), _src1->GetSourceId(), _src2->GetSourceId() );
			data.source << " )\n";
			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			return SrcFunc::Native< Real, NativeFunction >( vPoint, _src0, _src1, _src2 );
		}

		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};



	//
	// Clamp
	//

	struct Math_Clamp
	{
		static void Build (CSource::TFunctionData &data, uint src0, uint src1, uint src2)
		{
			data.source << "clamp( ";
			CSource::BuildFunctionCall( data.source, src0 );
			data.source << ", ";
			CSource::BuildFunctionCall( data.source, src1 );
			data.source << ", ";
			CSource::BuildFunctionCall( data.source, src2 );
			data.source << " )";
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		static Real Native (const TVec<Real,3> &vPoint, CSourcePtr src0, CSourcePtr src1, CSourcePtr src2)
		{
			return	Clamp(	(src0.ptr()->*NativeFunction)( vPoint ),
							(src1.ptr()->*NativeFunction)( vPoint ),
							(src2.ptr()->*NativeFunction)( vPoint ) );
		})
	};

	typedef CMathSource3Args< Math_Clamp, CLAMP >	CSourceClamp;



	//
	// Mad
	//

	struct Math_Mad
	{
		static void Build (CSource::TFunctionData &data, uint src0, uint src1, uint src2)
		{
			data.source << "mad( ";
			CSource::BuildFunctionCall( data.source, src0 );
			data.source << ", ";
			CSource::BuildFunctionCall( data.source, src1 );
			data.source << ", ";
			CSource::BuildFunctionCall( data.source, src2 );
			data.source << " )";
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		static Real Native (const TVec<Real,3> &vPoint, CSourcePtr src0, CSourcePtr src1, CSourcePtr src2)
		{
			return	(src0.ptr()->*NativeFunction)( vPoint ) *
					(src1.ptr()->*NativeFunction)( vPoint ) +
					(src2.ptr()->*NativeFunction)( vPoint );
		})
	};

	typedef CMathSource3Args< Math_Mad, MAD >	CSourceMad;



	//
	// Mix (Blend, Lerp, LinearInterpolation)
	//

	struct Math_Mix
	{
		static void Build (CSource::TFunctionData &data, uint src0, uint src1, uint src2)
		{
			data.source << "mix( ";
			CSource::BuildFunctionCall( data.source, src0 );
			data.source << ", ";
			CSource::BuildFunctionCall( data.source, src1 );
			data.source << ", ";
			CSource::BuildFunctionCall( data.source, src2 );
			data.source << " )";
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		static Real Native (const TVec<Real,3> &vPoint, CSourcePtr src0, CSourcePtr src1, CSourcePtr src2)
		{
			return Mix( (src0.ptr()->*NativeFunction)( vPoint ),
						(src1.ptr()->*NativeFunction)( vPoint ),
						(src2.ptr()->*NativeFunction)( vPoint ) );
		})
	};

	typedef CMathSource3Args< Math_Mix, MIX >	CSourceMix;

}	// Noise